//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================
#include "ddk.h"
#include "CBasicFile.h"
#define IncrementDllLockCount() do {} while(0)
#define DecrementDllLockCount() do {} while(0)
#include "_CKtfsFile.cpp"

#include "FileOps.h"
#include <fatfile.h>
#include <stdlib.h>
#include <limits.h>
#include <ChangeNotifier.h>

#define SIZE_MAX LONG_MAX

CKtfsFile::~CKtfsFile()
{
    if (m_pCFatFile) {
        SetShareMode(FileShareMode_Write);
        m_pCFatFile->Release();
    }
}

ECode CKtfsFile::Read(
    /* [in] */ Int32 offset,
    /* [in] */ Int32 toRead,
    /* [out] */ MemoryBuf * pBuf)
{
    if (pBuf == NULL || !pBuf->GetCapacity() || offset < 0) {
        return E_INVALID_ARGUMENT;
    }

    if (offset > (Int32)m_pCFatFile->f_node->n_len \
        || (UInt32)toRead > SIZE_MAX || toRead > pBuf->GetCapacity()) {
        return ErrorToECODE(EINVAL, "CKtfsFile::Read");
    }

    Lock();
    if (m_pCFatFile->f_node->n_sharemode == FileShareMode_Deny &&
        (UInt32)m_pCFatFile != m_pCFatFile->f_node->n_firsthandle) {
        Unlock();
        return ErrorToECODE(EBUSY, "CKtfsFile::Read");
    }
    m_pCFatFile->f_pos = offset;

    UInt32 uReadSize = 0;

    int nRen = m_pCFatFile->pFatDir->fatfs_read(m_pCFatFile, \
                            (unsigned char *)pBuf->GetPayload(), \
                            toRead, &uReadSize);

    Unlock();

    pBuf->SetUsed(uReadSize);
    return ErrorToECODE(nRen, "CKtfsFile::Read");
}

ECode CKtfsFile::Write(
    /* [in] */ Int32 offset,
    /* [in] */ const MemoryBuf & ezbBuf,
    /* [out] */ Int32 * pWrite)
{
    if (ezbBuf.IsNullOrEmpty() || NULL == pWrite || offset < 0) {
        return E_INVALID_ARGUMENT;
    }
    DEBUGOUT(kprintf("In Write:file:%d,nclust:%d, %s\n",
                    m_pCFatFile->f_node->n_slot,
                    m_pCFatFile->f_node->n_clust->c_nclust,
                    (char *)ezbBuf.GetPayload());
    )

    Lock();
    if (m_pCFatFile->f_node->n_sharemode != FileShareMode_Write &&
        (UInt32)m_pCFatFile != m_pCFatFile->f_node->n_firsthandle) {
        Unlock();
        return ErrorToECODE(EBUSY, "CKtfsFile::Write");
    }
    m_pCFatFile->f_pos = offset;
    m_pCFatFile->f_perm = m_pCFatFile->f_node->n_mode;

    UInt32 nWritten = 0;

    int nRen = m_pCFatFile->pFatDir->fatfs_write(m_pCFatFile, \
                                (unsigned char *)ezbBuf.GetPayload(), \
                                ezbBuf.GetUsed(), &nWritten);

    Unlock();

    if (!nRen) {
        assert(nWritten == (UInt32)ezbBuf.GetUsed());
        *pWrite = nWritten;
        return NOERROR;
    }
    else {
        return ErrorToECODE(nRen, "CKtfsFile::Write");
    }
}

ECode CKtfsFile::GetSize(
    /* [out] */ Int32 * pSize)
{
    if (NULL == pSize) {
        return E_INVALID_ARGUMENT;
    }

    Lock();
    if (m_pCFatFile->f_node->n_sharemode == FileShareMode_Deny &&
        (UInt32)m_pCFatFile != m_pCFatFile->f_node->n_firsthandle) {
        Unlock();
        return ErrorToECODE(EBUSY, "CKtfsFile::GetSize");
    }
    *pSize = m_pCFatFile->f_node->n_len;
    Unlock();
    return NOERROR;
}

ECode CKtfsFile::SetSize(
    /* [in] */ Int32 size)
{
    int nError;
    DEBUGOUT(kprintf("In setsize:%d\n", size);)
    if (size < 0) return E_INVALID_ARGUMENT;
    m_pCFatFile->f_perm = m_pCFatFile->f_node->n_mode;

    Lock();
    if (m_pCFatFile->f_node->n_sharemode != FileShareMode_Write &&
        (UInt32)m_pCFatFile != m_pCFatFile->f_node->n_firsthandle) {
        Unlock();
        return ErrorToECODE(EBUSY, "CKtfsFile::SetSize");
    }

    nError = m_pCFatFile->pFatDir->fatfs_SetSize(m_pCFatFile, size);

    if (nError) {
        Unlock();
        return ErrorToECODE(nError, "CKtfsFile::SetSize");
    }

    m_pCFatFile->pFatDir->sync();
    Unlock();
    return NOERROR;
}

ECode CKtfsFile::GetOptimalBlockSize(
    /* [out] */ Int32 * pSize)
{
    if (NULL == pSize) {
        return E_INVALID_ARGUMENT;
    }

    *pSize = m_pCFatFile->pFatDir->p_Sharedata->clsize;
    return NOERROR;
}

ECode CKtfsFile::Sync()
{
    Lock();
    m_pCFatFile->pFatDir->Syncn(m_pCFatFile->f_node);
    Unlock();
    return NOERROR;
}

ECode CKtfsFile::GetType(
    /* [out] */ FileType * pType)
{
    ECode ec;
    Lock();

    if (m_pCFatFile->f_node->n_sharemode == FileShareMode_Deny &&
        (UInt32)m_pCFatFile != m_pCFatFile->f_node->n_firsthandle) {
        Unlock();
        return ErrorToECODE(EBUSY, "CKtfsFile::GetType");
    }
    ec = FGetType(m_pCFatFile->f_node, pType);
    Unlock();
    return ec;
}

ECode CKtfsFile::GetAccessTime(
    /* [out] */ Int32 * pTime)
{
    ECode ec;
    Lock();
    if (m_pCFatFile->f_node->n_sharemode == FileShareMode_Deny &&
        (UInt32)m_pCFatFile != m_pCFatFile->f_node->n_firsthandle) {
        Unlock();
        return ErrorToECODE(EBUSY, "CKtfsFile::GetAccessTime");
    }
    ec = FGetAccessTime(m_pCFatFile, pTime);
    Unlock();
    return ec;
}

ECode CKtfsFile::SetAccessTime(
    /* [in] */ Int32 time)
{
    ECode ec;
    Lock();
    if (m_pCFatFile->f_node->n_sharemode != FileShareMode_Write &&
        (UInt32)m_pCFatFile != m_pCFatFile->f_node->n_firsthandle) {
        Unlock();
        return ErrorToECODE(EBUSY, "CKtfsFile::SetAccessTime");
    }

    ec = FSetAccessTime(m_pCFatFile, time);
    Unlock();
    return ec;
}

ECode CKtfsFile::GetModificationTime(
    /* [out] */ Int32 * pTime)
{
    ECode ec;
    Lock();
    if (m_pCFatFile->f_node->n_sharemode == FileShareMode_Deny &&
        (UInt32)m_pCFatFile != m_pCFatFile->f_node->n_firsthandle) {
        Unlock();
        return ErrorToECODE(EBUSY, "CKtfsFile::GetModificationTime");
    }

    ec = FGetModificationTime(m_pCFatFile, pTime);
    Unlock();
    return ec;
}

ECode CKtfsFile::SetModificationTime(
    /* [in] */ Int32 time)
{
    ECode ec;
    Lock();
    if (m_pCFatFile->f_node->n_sharemode != FileShareMode_Write &&
        (UInt32)m_pCFatFile != m_pCFatFile->f_node->n_firsthandle) {
        Unlock();
        return ErrorToECODE(EBUSY, "CKtfsFile::SetModificationTime");
    }

    ec = FSetModificationTime(m_pCFatFile, time);
    Unlock();
    return ec;
}

ECode CKtfsFile::GetCreateTime(
    /* [out] */ Int32 * pTime)
{
    ECode ec;
    Lock();
    if (m_pCFatFile->f_node->n_sharemode == FileShareMode_Deny &&
        (UInt32)m_pCFatFile != m_pCFatFile->f_node->n_firsthandle) {
        Unlock();
        return ErrorToECODE(EBUSY, "CKtfsFile::GetCreateTime");
    }

    ec = FGetCreateTime(m_pCFatFile, pTime);
    Unlock();
    return ec;
}

ECode CKtfsFile::CheckAccessMode(
    /* [in] */ FileAccessMode mode)
{
    ECode ec;
    Lock();
    ec = FCheckAccessMode(m_pCFatFile, mode);
    Unlock();
    return ec;
}

ECode CKtfsFile::GetAccessMode(
    /* [out] */ FileAccessMode * pMode)
{
    ECode ec;
    Lock();
    if (m_pCFatFile->f_node->n_sharemode == FileShareMode_Deny &&
        (UInt32)m_pCFatFile != m_pCFatFile->f_node->n_firsthandle) {
        Unlock();
        return ErrorToECODE(EBUSY, "CKtfsFile::GetAccessMode");
    }
    ec = FGetAccessMode(m_pCFatFile, pMode);
    Unlock();
    return ec;
}

ECode CKtfsFile::SetAccessMode(
    /* [in] */ FileAccessMode mode)
{
    ECode ec;
    Lock();
    if (m_pCFatFile->f_node->n_sharemode != FileShareMode_Write &&
        (UInt32)m_pCFatFile != m_pCFatFile->f_node->n_firsthandle) {
        Unlock();
        return ErrorToECODE(EBUSY, "CKtfsFile::SetAccessMode");
    }

    ec = FSetAccessMode(m_pCFatFile, mode);
    Unlock();
    return ec;
}

ECode CKtfsFile::GetShareMode(
    /* [out] */ FileShareMode * pMode)
{
    ECode ec = NOERROR;
    Lock();
    ec = FGetShareMode(m_pCFatFile, pMode);
    Unlock();
    return ec;
}

ECode CKtfsFile::SetShareMode(
    /* [in] */ FileShareMode mode)
{
    ECode ec = NOERROR;
    Lock();
    ec = FSetShareMode(m_pCFatFile, mode);
    Unlock();
    return ec;
}

ECode CKtfsFile::AddFileMonitor(
    /* [out] */ IEvent** ppEvent)
{
    if (NULL == ppEvent)
        return E_INVALID_ARGUMENT;

    CChangeNotifier *pNotifier = m_pCFatFile->pFatDir->p_Sharedata->pNotifier;
    int error = 0;

    if (NULL == pNotifier) {
        pNotifier = new CChangeNotifier;
        if (NULL == pNotifier)
            return E_OUT_OF_MEMORY;
    }

    error = pNotifier->AddMonitor(m_pCFatFile->f_node, ppEvent);

    if (error != 0)
        return ErrorToECODE(error, "CKtfsFile::AddFileMonitor");
    /* Make sure node who has been monitored could not be Release. */
    m_pCFatFile->pFatDir->ref_node(m_pCFatFile->f_node);
    m_pCFatFile->pFatDir->p_Sharedata->pNotifier = pNotifier;

    return NOERROR;
}

ECode CKtfsFile::RemoveFileMonitor(
    /* [in] */ IEvent * pEvent)
{
    CChangeNotifier *pNotifier = m_pCFatFile->pFatDir->p_Sharedata->pNotifier;
    int error = 0;

    if (NULL == pNotifier) {
        return ErrorToECODE(EIO, "CKtfsDirectory::RemoveFileMonitor");
    }

    if (NULL == pEvent)
        return E_INVALID_ARGUMENT;

    error = pNotifier->RemoveMonitor(m_pCFatFile->f_node, pEvent);
    if (error != 0)
        return ErrorToECODE(error, "CKtfsFile::RemoveFileMonitor");

    error = m_pCFatFile->pFatDir->deref_node(m_pCFatFile->f_node);
    if (error != 0)
        return ErrorToECODE(error, "CKtfsFile::RemoveFileMonitor");

    return NOERROR;
}

inline void CKtfsFile::Lock()
{
    Ktfs_Lock(&this->m_pCFatFile->f_node->n_lock);
}

inline void CKtfsFile::Unlock()
{
    Ktfs_Unlock(&this->m_pCFatFile->f_node->n_lock);
}
